package joc.transformer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.annotation.NoSuchClassError;
import joc.contract.Contract;
import joc.contract.ForParameterInsertAtBeginningOfMethod;

public class ParameterPreConditionMethodTransformer extends DeclaredMethodTransformer {

	public ParameterPreConditionMethodTransformer(Transformer transformer) {
		super(transformer);
	}

	@Override
	public void transform(CtClass clazz, CtMethod method) throws Exception {
		logger.debug("method " + method.getLongName());
		for (int argNum = 1; argNum <= method.getParameterTypes().length; argNum++) {
			logger.debug(" parameter " + argNum);
			for (Class<? extends Annotation> contractAnnotation : Transformer.getContractMap().keySet()) {
				Annotation annotation = getParameterAnnotation(method, argNum, contractAnnotation);
				if (annotation != null) {
					logger.info("method " + method.getLongName() + " parameter " + argNum + " has contract " + Transformer.getContractMap().get(contractAnnotation).getClass().getName());
					applyContractForParameter(method, Transformer.getContractMap().get(contractAnnotation), argNum, annotation);
				}
			} 
		}
	}

	private void applyContractForParameter(CtMethod method, Contract contract, int argNum, Annotation annotation) throws Exception {
		for (Method contractMethod : getContractMethodsWithAnnotation(contract, ForParameterInsertAtBeginningOfMethod.class)) {
			method.insertBefore(getContractCallString(contractMethod, contract, annotation, "joc.Invoker.ObjectConverter#toObject($" + argNum + ")"));
			logger.info("inserted " + contract.getClass().getName() + " in method " + method.getLongName() + " at parameter " + argNum);
		}
	}
	
	private Annotation getParameterAnnotation(CtMethod method, int argNum, Class<? extends Annotation> annotationClass) throws ClassNotFoundException, NoSuchClassError {
		MethodInfo mi = method.getMethodInfo2();
		ParameterAnnotationsAttribute ainfo = (ParameterAnnotationsAttribute) mi.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
		ParameterAnnotationsAttribute ainfo2 = (ParameterAnnotationsAttribute) mi.getAttribute(ParameterAnnotationsAttribute.visibleTag);
		Annotation result = parameterAnnotationsIncludeClass(argNum, annotationClass, ainfo);
		if (result != null) {
			return result;
		}
		return parameterAnnotationsIncludeClass(argNum, annotationClass, ainfo2);
	}

	private Annotation parameterAnnotationsIncludeClass(int argNum,
			Class<?> annotationClass, ParameterAnnotationsAttribute ainfo) throws ClassNotFoundException, NoSuchClassError {
		if (ainfo != null && ainfo.getAnnotations()[argNum - 1] != null) {
			for (javassist.bytecode.annotation.Annotation annotation : ainfo.getAnnotations()[argNum - 1]) {
				if (annotation.getTypeName().equals(annotationClass.getName())) {
					return (Annotation) annotation.toAnnotationType(transformer.getPool().getClassLoader(), transformer.getPool());
				}
			}
		}
		return null;
	}

}
